package models.crawlers;

import com.google.gson.Gson;
import models.crawlers.common.WebSiteInfo;
import models.crawlers.events.CategoryProcessed;
import models.crawlers.events.ProductProcessed;
import models.crawlers.events.ScanCompleted;
import models.entities.*;
import models.exceptions.UpdateEntityException;
import models.helpers.ImageHelper;
import models.repository.CategoryRepositorySQL;
import models.repository.CrawlerRepositorySQL;
import models.repository.ImageRepositorySql;
import models.repository.ProductRepositorySQL;
import models.repository.common.CategoryRepository;
import models.repository.common.CrawlerRepository;
import models.repository.common.ImageRepository;
import models.repository.common.ProductRepository;
import play.Logger;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

public class CrawlerEventObserver implements Observer {
    ProductRepository productRepository = new ProductRepositorySQL();
    CategoryRepository categoryRepository = new CategoryRepositorySQL();
    private String dataPath;
    private CrawlerEntity crawlerInfo;

    public CrawlerEventObserver(CrawlerEntity crawlerEntity, String dataPath) {
        this.dataPath = dataPath;
        this.crawlerInfo = crawlerEntity;
    }

    public void update(Observable o, Object eventObject) {
        if (eventObject instanceof WebSiteInfo) {
            updateCrawlerPosition((WebSiteInfo) eventObject);
        } else if (eventObject instanceof ProductProcessed) {
            saveProduct((ProductProcessed) eventObject);
        } else if (eventObject instanceof CategoryProcessed) {
            getCategory((CategoryProcessed) eventObject);
        } else if (eventObject instanceof ScanCompleted) {
            markAsCompleted((ScanCompleted) eventObject);
        }
    }

    private void markAsCompleted(ScanCompleted eventObject) {
        if (!eventObject.isSuspended) {
            crawlerInfo.status = CrawlerEntity.CrawlerStatuses.Completed;
            crawlerInfo.productsCount = eventObject.webSiteInfo.productIndex;
            crawlerInfo.categoriesCount = eventObject.webSiteInfo.categoryIndex;
            Gson gson = new Gson();
            eventObject.webSiteInfo.productsCount = eventObject.webSiteInfo.productIndex;
            eventObject.webSiteInfo.categoriesCount = eventObject.webSiteInfo.categoryIndex;
            crawlerInfo.webSiteInfo = gson.toJson(eventObject.webSiteInfo);
        } else {
            crawlerInfo.status = CrawlerEntity.CrawlerStatuses.Stopped;
        }
        try {
            CrawlerRepository crawlerRepository = new CrawlerRepositorySQL();
            //TODO: Refactor these two calls into one call
            crawlerRepository.setProductCount(crawlerInfo);
            crawlerRepository.updateCrawlerInfo(crawlerInfo);
        } catch (Exception e) {
            Logger.error(e, "mark as completed");
            e.printStackTrace();
        }
    }

    private void getCategory(CategoryProcessed eventObject) {
        try {
            CategoryEntity existentCategory = categoryRepository.getByTitle(eventObject.category.title);
            if (existentCategory == null) {
                categoryRepository.create(eventObject.category);
            } else {
                eventObject.category.id = existentCategory.id;
            }
        } catch (Exception e) {
            e.printStackTrace();
            Logger.error(e, "The error occurred while saving category from internet");
        }
    }

    private void saveProduct(ProductProcessed productInfo) {
        try {
            saveProduct(productInfo.product);
            saveSource(productInfo.url, productInfo.product, productInfo.sourceId);
            clearInvalidPictures(productInfo.product.id);
            savePictures(productInfo.product);
        } catch (Exception e) {
            e.printStackTrace();
            Logger.error(e, "The error occurred while saving product from internet");
        }
    }

    private ProductSourceEntity saveSource(String productUrl, ProductEntity product, String sourceId) throws Exception {
        ProductSourceEntity productSource = new ProductSourceEntity();
        productSource.crawlerName = this.getClass().getSimpleName();
        productSource.title = product.title;
        productSource.url = productUrl;
        productSource.sourceId = sourceId;
        productSource.productId = product.id;
        ProductSourceEntity existentProductSource = productRepository.getProductSource(productSource);
        if (existentProductSource == null) {
            productRepository.addProductSource(productSource);
        } else {
            productRepository.updateProductSource(productSource);
        }
        return productSource;
    }

    protected void clearInvalidPictures(Integer id) throws IOException {
        ProductRepository repository = new ProductRepositorySQL();
        List<PictureEntity> pictures = repository.getAllProductImages(id);
        for (PictureEntity picture : pictures) {
            if (!ImageHelper.isImageValid(picture, dataPath)) {
                repository.deleteImage(picture);
                ImageHelper.clearProductImageCache(picture, id, dataPath);
            }
        }
    }

    private void saveProduct(ProductEntity productEntity) throws Exception {
        ProductEntity existentProduct = productRepository.getByTitle(productEntity.title);
        if (existentProduct == null) {
            productRepository.create(productEntity);
        } else {
            productEntity.id = existentProduct.id;
            productRepository.update(productEntity);
            boolean descriptionChanged = false;
            if (productEntity.description != null && existentProduct.description != null)
                descriptionChanged = !productEntity.description.equals(existentProduct.description);
            boolean shortDescriptionChanged = false;
            if (productEntity.shortDescription != null && existentProduct.shortDescription != null)
                shortDescriptionChanged = !productEntity.shortDescription.equals(existentProduct.shortDescription);
        }

    }

    protected BufferedImage getPictureData(String remoteUrl) throws IOException {
        if (remoteUrl == "")
            return null;
        URL url = new URL(remoteUrl);
        try {
            BufferedImage image = ImageIO.read(url);
            return image;
        } catch (IOException ex) {
            return null;
        }
    }

    protected void savePictures(ProductEntity productEntity) throws Exception {
        for (String pictureUrl : productEntity.pictureUrls) {
            BufferedImage pictureData = getPictureData(pictureUrl);
            if (pictureData != null) {
                PictureEntity image = new PictureEntity(productEntity.id, pictureData, "jpg");
                image.md5 = ImageHelper.getMD5HashCode(image.pictureData);
                ImageRepository imageRepository = new ImageRepositorySql();
                PictureEntity dbImage = imageRepository.getImageByMD5(image.md5);
                if (dbImage == null) {
                    productRepository.addPictureToProduct(image, dataPath);
                } else {
                    PictureEntity picture = imageRepository.getProductImage(dbImage.id, productEntity.id);
                    if (picture == null)
                        productRepository.addExistingImageToProduct(productEntity.id, dbImage.id);
                }
            }
        }
    }

    private void updateCrawlerPosition(WebSiteInfo webSiteInfo) {
        try {
            Gson gson = new Gson();
            String serializedWebSiteInfo = gson.toJson(webSiteInfo);
            CrawlerRepository crawlerRepository = new CrawlerRepositorySQL();
            crawlerInfo.webSiteInfo = serializedWebSiteInfo;
            crawlerInfo.status = CrawlerEntity.CrawlerStatuses.Running;
            crawlerInfo.categoriesCount = webSiteInfo.categoryIndex;
            crawlerInfo.productsCount = webSiteInfo.productsCount;
            crawlerInfo.productIndex = webSiteInfo.productIndex;
            crawlerRepository.updateCrawlerInfo(crawlerInfo);
        } catch (UpdateEntityException e) {
            e.printStackTrace();
            Logger.error(e, "update crawler info");
        } catch (IOException e) {
            e.printStackTrace();
            Logger.error(e, "update crawler");
        }

    }
}
